package com.jstarcraft.ai.jsat.linear;

import java.util.Random;

import com.jstarcraft.ai.jsat.utils.random.RandomUtil;

/**
 * Stores a Vector full of random values in constant O(1) space by re-computing
 * all matrix values on the fly as need. This allows memory reduction and use
 * when it is necessary to use the matrix with a large sparse data set, where
 * some matrix values may never even be used - or used very infrequently. <br>
 * <br>
 * Because the values of the random vector are computed on the fly, the Random
 * Vector can not be altered. If attempted, an exception will be thrown.
 * 
 * @author Edward Raff
 */
public abstract class RandomVector extends Vec {

    private static final long serialVersionUID = -1587968421978707875L;
    /*
     * Implementation note: It is assumed that the default random object is a PRNG
     * with a single word / long of state. A higher quality PRNG cant be used if it
     * requires too many words of state, as the initalization will then dominate the
     * computation of every index.
     */
    private int length;
    private long seedMult;

    /**
     * Creates a new Random Vector object
     * 
     * @param length the length of the vector
     */
    public RandomVector(int length) {
        this(length, RandomUtil.getRandom().nextLong());
    }

    /**
     * Creates a new Random Vector object
     * 
     * @param length   the length of the vector
     * @param seedMult a value to multiply with the seed used for each individual
     *                 index. It should be a large value
     */
    public RandomVector(int length, long seedMult) {
        if (length <= 0)
            throw new IllegalArgumentException("Vector length must be positive, not " + length);
        this.length = length;
        this.seedMult = seedMult;
    }

    /**
     * Copy constructor
     * 
     * @param toCopy the object to copy
     */
    protected RandomVector(RandomVector toCopy) {
        this(toCopy.length, toCopy.seedMult);
    }

    private ThreadLocal<Random> localRand = new ThreadLocal<Random>() {
        @Override
        protected Random initialValue() {
            return new Random(1);// seed will get set by user
        }
    };

    /**
     * Computes the value of an index given the already initialized {@link Random}
     * object. This is called by the {@link #get(int) } method, and will make sure
     * that the correct seed is set before calling this method.
     * 
     * @param rand the PRNG to generate the index value from
     * @return the value for a given index based on the given PRNG
     */
    abstract protected double getVal(Random rand);

    @Override
    public double get(int index) {
        long seed = (index + length) * seedMult;
        Random rand = localRand.get();
        rand.setSeed(seed);
        return getVal(rand);
    }

    @Override
    public void set(int index, double val) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public int length() {
        return length;
    }

    @Override
    public void multiply(double c, Matrix A, Vec b) {
        if (this.length() != A.rows())
            throw new ArithmeticException("Vector x Matrix dimensions do not agree [1," + this.length() + "] x [" + A.rows() + ", " + A.cols() + "]");
        if (b.length() != A.cols())
            throw new ArithmeticException("Destination vector is not the right size");

        for (int i = 0; i < this.length(); i++) {
            double this_i = c * get(i);
            for (int j = 0; j < A.cols(); j++)
                b.increment(j, this_i * A.get(i, j));
        }
    }

    @Override
    public void mutableAdd(double c) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public void mutableAdd(double c, Vec b) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public void mutablePairwiseMultiply(Vec b) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public void mutableMultiply(double c) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public void mutablePairwiseDivide(Vec b) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public void mutableDivide(double c) {
        throw new UnsupportedOperationException("RandomVector can not be altered");
    }

    @Override
    public Vec sortedCopy() {
        DenseVector dv = new DenseVector(this);
        return dv.sortedCopy();
    }

    @Override
    public double min() {
        double min = Double.MAX_VALUE;
        for (IndexValue iv : this)
            min = Math.min(iv.getValue(), min);
        return min;
    }

    @Override
    public double max() {
        double max = -Double.MAX_VALUE;
        for (IndexValue iv : this)
            max = Math.min(iv.getValue(), max);
        return max;
    }

    @Override
    public boolean isSparse() {
        return false;
    }

    @Override
    abstract public Vec clone();

    @Override
    public double dot(Vec v) {
        double dot = 0;

        for (IndexValue iv : v)
            dot += get(iv.getIndex()) * iv.getValue();
        return dot;
    }

    @Override
    public boolean canBeMutated() {
        return false;
    }

    @Override
    public void setLength(int length) {
        this.length = length;
    }

}
